前面有提過,javascript 是「同步」的,但是如果 javascript 是同步的話,要如何執行非同步事件?
首先我們要先知道,瀏覽器內不只有 javascript 引擎,另外還有像是 render 引擎和處理 HTTP 等
當有個 javascript 引擎外,有個需要等待被通知的事件,會被放在 javascript 等待列,又稱為事件佇列(event queue)
例如有個點擊事件,如 click 的回應函數 ,又或是透過 HTTP request 取得資料,都會會先被放在等待列
javascript 會先完成執行堆的上的任務,才會去看事件佇列有沒有事件
以上圖舉例,當執行堆上的任務都清空後,看到 click 事件,發現有個函數需執行,所以一個創建一個函數的環境給它
當執行完 click 的事件函數後,執行堆又清空,於是再看看事件佇列,繼續執行下一個事件
來看看程式碼 jsbin
function waitThreeSeconds() {
var ms = 3000 + new Date().getTime()
while (new Date() < ms) {}
console.log('finish function')
}
function clickHandler() {
console.log('click event!')
}
document.addEventListener('click', clickHandler)
waitThreeSeconds();
console.log('finish execution')
有一個要等待 3 秒才 console.log 的函數,和一個點擊事件。
實際執行程式碼,先不點擊頁面,可以看到網頁需要花 3 秒才載入完成,另外 console 的順序是先印出等待 3 秒後的 waitThreeSeconds
函數,才執行 console.log('finish execution')
,因為 javascript 是同步執行的
// console
finish function
finish execution
再試試如果當函數正在執行時點擊了頁面,console.log 順序會是什麼?
// console
finish function
finish execution
click event!
可以看到點擊事件會在執行完全域的程式碼後才執行,因為就如前面所說,點擊事件會被放在事件佇列,等執行堆空了才去看,
當事件佇列也結束後,會再不斷的查看事件佇列是否有新的事件,有的話便執行,這稱為「持續檢查(continuous check)」。
這就是 Javascript 執行非同步的方式,只有針對 Javascript 引擎的事件,透過放在事件佇列的方式,等待執行堆清空才去查看,然後同步的處理它們
後記
關於非同步還有事件佇列的觀念比較複雜跟難理解一點,這篇雖然不長,花了我蠻多時間理解,但還不是很完整,許願自己之後好好針對這個主題寫出比較完整的文章
延伸閱讀:
What the heck is the event loop anyway? | Philip Roberts | JSConf EU